Appearance
[中文版本](/posts/c教程/zh-CN/07-贴近系统/061-System Calls and the Standard Library)
When you write C programs that touch files, processes, or devices, you’re talking to the operating system, not directly to hardware. That communication happens through system calls.
System calls are the lowest-level interface between user-space programs and the OS kernel. C’s standard library (libc) is a thin layer of wrappers built on top of those calls, making them easier to use and more portable.
Let’s explore how this works, and how to use system calls directly from your C code.
Step 1. What Is a System Call?
A system call (syscall) lets a program request a service from the OS kernel, like reading a file, creating a process, or allocating memory.
Examples:
read(),write()– access files and devicesfork(),exec()– create and manage processesopen(),close()– handle file descriptorsmmap()– map files into memorysocket()– network communication
When you call a system function, control passes from user space to kernel space, then back again.
Step 2. The Role of libc
The C standard library (glibc, musl, etc.) provides wrappers around these system calls.
Example:
#include <stdio.h>
int main(void) {
FILE *f = fopen("test.txt", "r");
if (!f) {
perror("fopen failed");
return 1;
}
fclose(f);
}Under the hood,fopen() eventually callsopen(), a system call defined in<fcntl.h>. You can call it directly too.
Step 3. Calling System Calls Directly
Here’s the same operation without stdio helpers:
#include <fcntl.h> // open
#include <unistd.h> // read, write, close
#include <stdio.h> // perror
int main(void) {
int fd = open("test.txt", O_RDONLY);
if (fd == -1) {
perror("open failed");
return 1;
}
char buf[128];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n >= 0) {
buf[n] = '\0';
write(STDOUT_FILENO, buf, n);
}
close(fd);
}Compile and run:
gcc sysread.c -o sysread
./sysreadThis prints the first 128 bytes oftest.txt directly using system calls, nofopen() orprintf() involved.
Tiny Code: Minimal System Call Example
Let’s drop even the C library and use a raw syscall interface.
#include <unistd.h>
int main(void) {
const char msg[] = "Hello via system call\n";
write(1, msg, sizeof(msg) - 1); // 1 = STDOUT
_exit(0);
}Compile:
gcc -nostdlib -static syshello.c -o syshelloRun:
Hello via system callYou just executed a system call directly, bypassing the standard library entirely.
Step 4. File Descriptors
System calls likeread() andwrite() work with file descriptors, small integer handles managed by the OS.
| Descriptor | Meaning |
|---|---|
| 0 | Standard input (stdin) |
| 1 | Standard output (stdout) |
| 2 | Standard error (stderr) |
Every open file, socket, or pipe has a unique descriptor.
Example:
write(1, "Output to stdout\n", 17);
write(2, "Output to stderr\n", 17);Step 5. Inspecting System Calls
You can watch your program’s system calls usingstrace:
strace ./sysreadExample output:
open("test.txt", O_RDONLY) = 3
read(3, "Hello World\n", 12) = 12
write(1, "Hello World\n", 12) = 12
close(3) = 0This shows the real kernel-level operations, a great debugging and learning tool.
Step 6. Return Values and Errors
System calls usually return:
- ≥ 0 → success (bytes read, process ID, etc.)
- -1 → error (with
errnoset to an error code)
Example:
#include <errno.h>
#include <string.h>
int fd = open("missing.txt", O_RDONLY);
if (fd == -1)
fprintf(stderr, "Error: %s\n", strerror(errno));Common error codes:
| Code | Meaning |
|---|---|
ENOENT | File not found |
EACCES | Permission denied |
EBADF | Invalid descriptor |
EINTR | Interrupted system call |
Step 7. Mixing System Calls and stdio
You can combine both layers safely, just don’t mix them on the same file descriptor.
Example (safe):
FILE *f = fopen("log.txt", "w");
write(STDOUT_FILENO, "Console log\n", 12);
fprintf(f, "File log\n");
fclose(f);Example (unsafe):
FILE *f = fopen("data.txt", "w");
write(fileno(f), "mixed output\n", 13); // may confuse bufferingStep 8. System Call Wrappers
The Linux kernel provides hundreds of system calls. You can call most through<unistd.h>, but for rare ones, you can usesyscall():
#include <sys/syscall.h>
#include <unistd.h>
int main(void) {
syscall(SYS_write, 1, "Hello syscall\n", 14);
return 0;
}Step 9. Viewing Available Syscalls
Check all available system calls for your platform:
man 2 introor
ausyscall --dump | headYou’ll see a list like:
read, write, open, close, stat, fork, execve, mmap, ...Step 10. Why It Matters
System calls are the foundation of everything in C and Unix-like systems.
They give your program direct access to:
- Files and devices
- Processes and signals
- Memory and networking
- Time and environment
Learning to use them directly is essential for understanding how higher-level abstractions (likestdio,pthreads, or sockets) are built.
Try It Yourself
- Use
straceonlsorcatto see how system calls drive them. - Replace a
fopen()/fread()pair with directopen()andread()calls. - Write a tiny file copier using only
open,read,write, andclose. - Experiment with invalid file descriptors and print out
errno. - Build a version of
echothat uses only raw system calls.
Next, you’ll take this a step further: learning how to create and manage processes withfork() andexec(), the heart of Unix multitasking.
